/*
 *
 *  * Copyright (c) iwindplus Technologies Co., Ltd.2024-2030, All rights reserved.
 *
 *
 */

package com.iwindplus.base.operate.aspect;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.iwindplus.base.domain.constant.CommonConstant.ApiSignConstant;
import com.iwindplus.base.domain.enums.BizCodeEnum;
import com.iwindplus.base.domain.exception.BizException;
import com.iwindplus.base.domain.vo.ApiSignVO;
import com.iwindplus.base.operate.domain.annotation.ApiSign;
import com.iwindplus.base.operate.domain.property.OperateProperty;
import com.iwindplus.base.operate.domain.property.OperateProperty.ApiSignConfig;
import com.iwindplus.base.operate.service.OperateService;
import com.iwindplus.base.util.ApiSignUtil;
import com.iwindplus.base.util.DatesUtil;
import com.iwindplus.base.util.HttpsUtil;
import com.iwindplus.base.util.JacksonUtil;
import com.iwindplus.base.util.domain.dto.ApiSignVerifyDTO;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;

/**
 * API签名切面.
 *
 * @author zengdegui
 * @since 2025/07/13 17:02
 */
@Slf4j
@Aspect
@Order(10)
public class ApiSignAspect {

    @Resource
    private OperateProperty property;

    @Autowired(required = false)
    private OperateService operateService;

    /**
     * 切点
     */
    @Pointcut("@within(com.iwindplus.base.operate.domain.annotation.ApiSign) || @annotation(com.iwindplus.base.operate.domain.annotation.ApiSign)")
    public void pointCutMethod() {
    }

    /**
     * Before 切面
     *
     * @param joinPoint joinPoint
     */
    @Before("pointCutMethod()")
    public void beforePointCut(JoinPoint joinPoint) {
        final ApiSignConfig apiSign = this.property.getApiSign();
        if (Boolean.FALSE.equals(this.property.getEnabled())
            || Boolean.FALSE.equals(apiSign.getEnabled())
            || Objects.isNull(this.operateService)) {
            return;
        }

        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        ApiSign annotation = method.getAnnotation(ApiSign.class);
        if (Boolean.FALSE.equals(annotation.enabled())) {
            return;
        }

        final long beginMillis = System.currentTimeMillis();
        log.info("API签名，开始时间={}", DatesUtil.parseDate(beginMillis, DatePattern.NORM_DATETIME_MS_PATTERN));

        final HttpServletRequest request = HttpsUtil.getHttpServletRequest();
        if (ObjectUtil.isEmpty(request)) {
            return;
        }

        final String accessKey = request.getHeader(ApiSignConstant.X_ACCESS_KEY);
        if (CharSequenceUtil.isBlank(accessKey)) {
            throw new BizException(BizCodeEnum.ACCESS_KEY_NOT_EXIST);
        }

        try {
            final ApiSignVO data = this.operateService.getApiSign(accessKey);
            if (Objects.isNull(data)) {
                throw new BizException(BizCodeEnum.CONFIG_NOT_EMPTY);
            }

            this.checkApiSign(request, data);
        } finally {
            final long endMillis = System.currentTimeMillis();
            log.info("API签名, 结束时间={}，总执行毫秒数={}",
                DatesUtil.parseDate(endMillis, DatePattern.NORM_DATETIME_MS_PATTERN),
                endMillis - beginMillis);
        }
    }

    private void checkApiSign(HttpServletRequest request, ApiSignVO data) {
        String path = request.getHeader(ApiSignConstant.X_PATH);
        if (CharSequenceUtil.isBlank(path)) {
            path = request.getServletPath();
        }

        final String timestamp = request.getHeader(ApiSignConstant.X_TIMESTAMP);
        final String nonce = request.getHeader(ApiSignConstant.X_NONCE);
        final String sign = request.getHeader(ApiSignConstant.X_SIGN);
        ApiSignVerifyDTO entity = ApiSignVerifyDTO
            .builder()
            .accessKey(data.getAccessKey())
            .secretKey(data.getSecretKey())
            .timestamp(timestamp)
            .nonce(nonce)
            .path(path)
            .method(request.getMethod())
            .params(this.getParams(request))
            .sign(sign)
            .timeout(Duration.ofMinutes(data.getTimeout()))
            .build();
        final boolean verifySign = ApiSignUtil.verifySign(entity);
        if (!verifySign) {
            throw new BizException(BizCodeEnum.INVALID_SIGN);
        }
    }

    private Map<String, Object> getParams(HttpServletRequest req) {
        Map<String, Object> params = new HashMap<>(16);
        Optional.ofNullable(HttpsUtil.getParams(req)).ifPresent(params::putAll);
        Optional.ofNullable(HttpsUtil.getJsonParams(req))
            .filter(CharSequenceUtil::isNotBlank)
            .map(j -> JacksonUtil.parseObject(j, new TypeReference<Map<String, Object>>() {
            })).ifPresent(params::putAll);
        return params;
    }

}
